Skip to content

Reapply "[Clang][Sema] Use the correct lookup context when building overloaded 'operator->' in the current instantiation (#104458)" #109422

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jan 22, 2025

Conversation

sdkrystian
Copy link
Member

@sdkrystian sdkrystian commented Sep 20, 2024

Reapplies #104458, fixing a bug that occurs when a class member access expression calls an operator-> operator function that returns a non-dependent class type.

@sdkrystian sdkrystian requested a review from Endilll as a code owner September 20, 2024 13:48
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Sep 20, 2024
@llvmbot
Copy link
Member

llvmbot commented Sep 20, 2024

@llvm/pr-subscribers-clang

Author: Krystian Stasiowski (sdkrystian)

Changes

Reapplies #104458, fixing a bug that occurs when a templated class declares operator-> to return a non-dependent class type.


Full diff: https://github.com/llvm/llvm-project/pull/109422.diff

6 Files Affected:

  • (modified) clang/include/clang/Sema/Sema.h (+3-3)
  • (modified) clang/lib/Sema/SemaExprCXX.cpp (+18-18)
  • (modified) clang/lib/Sema/SemaExprMember.cpp (+4-2)
  • (modified) clang/lib/Sema/SemaOverload.cpp (+19-5)
  • (modified) clang/lib/Sema/TreeTransform.h (+6-2)
  • (modified) clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p4.cpp (+13-11)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 68c782a15c6f1b..c1afefd36ce0ce 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -10639,9 +10639,9 @@ class Sema final : public SemaBase {
   /// BuildOverloadedArrowExpr - Build a call to an overloaded @c operator->
   ///  (if one exists), where @c Base is an expression of class type and
   /// @c Member is the name of the member we're trying to find.
-  ExprResult BuildOverloadedArrowExpr(Scope *S, Expr *Base,
-                                      SourceLocation OpLoc,
-                                      bool *NoArrowOperatorFound = nullptr);
+  ExprResult BuildOverloadedArrowExpr(Expr *Base, SourceLocation OpLoc,
+                                      bool *NoArrowOperatorFound,
+                                      bool &IsDependent);
 
   ExprResult BuildCXXMemberCallExpr(Expr *Exp, NamedDecl *FoundDecl,
                                     CXXConversionDecl *Method,
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 14feafd1e6b17f..7f425b62b3e2cb 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -7966,18 +7966,6 @@ ExprResult Sema::ActOnStartCXXMemberReference(Scope *S, Expr *Base,
 
   QualType BaseType = Base->getType();
   MayBePseudoDestructor = false;
-  if (BaseType->isDependentType()) {
-    // If we have a pointer to a dependent type and are using the -> operator,
-    // the object type is the type that the pointer points to. We might still
-    // have enough information about that type to do something useful.
-    if (OpKind == tok::arrow)
-      if (const PointerType *Ptr = BaseType->getAs<PointerType>())
-        BaseType = Ptr->getPointeeType();
-
-    ObjectType = ParsedType::make(BaseType);
-    MayBePseudoDestructor = true;
-    return Base;
-  }
 
   // C++ [over.match.oper]p8:
   //   [...] When operator->returns, the operator-> is applied  to the value
@@ -7992,7 +7980,8 @@ ExprResult Sema::ActOnStartCXXMemberReference(Scope *S, Expr *Base,
     SmallVector<FunctionDecl*, 8> OperatorArrows;
     CTypes.insert(Context.getCanonicalType(BaseType));
 
-    while (BaseType->isRecordType()) {
+    while (
+        isa<InjectedClassNameType, RecordType>(BaseType.getCanonicalType())) {
       if (OperatorArrows.size() >= getLangOpts().ArrowDepth) {
         Diag(OpLoc, diag::err_operator_arrow_depth_exceeded)
           << StartingType << getLangOpts().ArrowDepth << Base->getSourceRange();
@@ -8002,15 +7991,26 @@ ExprResult Sema::ActOnStartCXXMemberReference(Scope *S, Expr *Base,
         return ExprError();
       }
 
+      bool IsDependent;
       Result = BuildOverloadedArrowExpr(
-          S, Base, OpLoc,
+          Base, OpLoc,
           // When in a template specialization and on the first loop iteration,
           // potentially give the default diagnostic (with the fixit in a
           // separate note) instead of having the error reported back to here
           // and giving a diagnostic with a fixit attached to the error itself.
           (FirstIteration && CurFD && CurFD->isFunctionTemplateSpecialization())
               ? nullptr
-              : &NoArrowOperatorFound);
+              : &NoArrowOperatorFound,
+          IsDependent);
+
+      if (IsDependent) {
+        // BuildOverloadedArrowExpr sets IsDependent to indicate that we need
+        // to build a dependent overloaded arrow expression.
+        assert(Base->isTypeDependent());
+        BaseType = Context.DependentTy;
+        break;
+      }
+
       if (Result.isInvalid()) {
         if (NoArrowOperatorFound) {
           if (FirstIteration) {
@@ -8030,6 +8030,7 @@ ExprResult Sema::ActOnStartCXXMemberReference(Scope *S, Expr *Base,
         }
         return ExprError();
       }
+
       Base = Result.get();
       if (CXXOperatorCallExpr *OpCall = dyn_cast<CXXOperatorCallExpr>(Base))
         OperatorArrows.push_back(OpCall->getDirectCallee());
@@ -8067,7 +8068,7 @@ ExprResult Sema::ActOnStartCXXMemberReference(Scope *S, Expr *Base,
   // it's legal for the type to be incomplete if this is a pseudo-destructor
   // call.  We'll do more incomplete-type checks later in the lookup process,
   // so just skip this check for ObjC types.
-  if (!BaseType->isRecordType()) {
+  if (BaseType->isDependentType() || !BaseType->isRecordType()) {
     ObjectType = ParsedType::make(BaseType);
     MayBePseudoDestructor = true;
     return Base;
@@ -8078,8 +8079,7 @@ ExprResult Sema::ActOnStartCXXMemberReference(Scope *S, Expr *Base,
   //   Unlike the object expression in other contexts, *this is not required to
   //   be of complete type for purposes of class member access (5.2.5) outside
   //   the member function body.
-  if (!BaseType->isDependentType() &&
-      !isThisOutsideMemberFunctionBody(BaseType) &&
+  if (!isThisOutsideMemberFunctionBody(BaseType) &&
       RequireCompleteType(OpLoc, BaseType,
                           diag::err_incomplete_member_access)) {
     return CreateRecoveryExpr(Base->getBeginLoc(), Base->getEndLoc(), {Base});
diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp
index f1ba26f38520a9..864e58cc7ee6ef 100644
--- a/clang/lib/Sema/SemaExprMember.cpp
+++ b/clang/lib/Sema/SemaExprMember.cpp
@@ -580,7 +580,9 @@ Sema::ActOnDependentMemberExpr(Expr *BaseExpr, QualType BaseType,
     }
   }
 
-  assert(BaseType->isDependentType() || NameInfo.getName().isDependentName() ||
+  assert(BaseType->isDependentType() ||
+         (BaseExpr && BaseExpr->isTypeDependent()) ||
+         NameInfo.getName().isDependentName() ||
          isDependentScopeSpecifier(SS) ||
          (TemplateArgs && llvm::any_of(TemplateArgs->arguments(),
                                        [](const TemplateArgumentLoc &Arg) {
@@ -1313,7 +1315,7 @@ static ExprResult LookupMemberExpr(Sema &S, LookupResult &R,
       BaseType = Ptr->getPointeeType();
     else if (BaseType->isFunctionType())
       goto fail;
-    else if (BaseType->isDependentType())
+    else if (BaseExpr.get()->isTypeDependent())
       BaseType = S.Context.DependentTy;
     else if (BaseType->isRecordType()) {
       // Recover from arrow accesses to records, e.g.:
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 861b0a91240b3b..6b91d4898674ec 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -15878,12 +15878,14 @@ Sema::BuildCallToObjectOfClassType(Scope *S, Expr *Obj,
   return CheckForImmediateInvocation(MaybeBindToTemporary(TheCall), Method);
 }
 
-ExprResult
-Sema::BuildOverloadedArrowExpr(Scope *S, Expr *Base, SourceLocation OpLoc,
-                               bool *NoArrowOperatorFound) {
-  assert(Base->getType()->isRecordType() &&
+ExprResult Sema::BuildOverloadedArrowExpr(Expr *Base, SourceLocation OpLoc,
+                                          bool *NoArrowOperatorFound,
+                                          bool &IsDependent) {
+  assert(Base->getType()->getAsRecordDecl() &&
          "left-hand side must have class type");
 
+  IsDependent = false;
+
   if (checkPlaceholderForOverload(*this, Base))
     return ExprError();
 
@@ -15904,7 +15906,19 @@ Sema::BuildOverloadedArrowExpr(Scope *S, Expr *Base, SourceLocation OpLoc,
     return ExprError();
 
   LookupResult R(*this, OpName, OpLoc, LookupOrdinaryName);
-  LookupQualifiedName(R, Base->getType()->castAs<RecordType>()->getDecl());
+  LookupParsedName(R, /*S=*/nullptr, /*SS=*/nullptr, Base->getType());
+
+  // If the expression is dependent and we either:
+  // - found a member of the current instantiation named 'operator->', or
+  // - found nothing, and the lookup context has no dependent base classes
+  //
+  // then we should build a dependent class member access expression.
+  if (R.wasNotFoundInCurrentInstantiation() ||
+      (Base->isTypeDependent() && !R.empty())) {
+    IsDependent = true;
+    return Base;
+  }
+
   R.suppressAccessDiagnostics();
 
   for (LookupResult::iterator Oper = R.begin(), OperEnd = R.end();
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 0daf620b4123e4..235b51a33babd5 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -16583,10 +16583,14 @@ ExprResult TreeTransform<Derived>::RebuildCXXOperatorCallExpr(
   } else if (Op == OO_Arrow) {
     // It is possible that the type refers to a RecoveryExpr created earlier
     // in the tree transformation.
-    if (First->getType()->isDependentType())
+    if (First->containsErrors())
       return ExprError();
+    bool IsDependent;
     // -> is never a builtin operation.
-    return SemaRef.BuildOverloadedArrowExpr(nullptr, First, OpLoc);
+    ExprResult Result = SemaRef.BuildOverloadedArrowExpr(
+        First, OpLoc, /*NoArrowOperatorFound=*/nullptr, IsDependent);
+    assert(!IsDependent);
+    return Result;
   } else if (Second == nullptr || isPostIncDec) {
     if (!First->getType()->isOverloadableType() ||
         (Op == OO_Amp && getSema().isQualifiedMemberAccess(First))) {
diff --git a/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p4.cpp b/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p4.cpp
index f32f49ef4539a5..89c22a0bc137d9 100644
--- a/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p4.cpp
+++ b/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.type/p4.cpp
@@ -484,16 +484,19 @@ namespace N4 {
   template<typename T>
   struct A {
     void not_instantiated(A a, A<T> b, T c) {
-      a->x;
-      b->x;
+      a->x; // expected-error {{member reference type 'A<T>' is not a pointer; did you mean to use '.'?}}
+      b->x; // expected-error {{member reference type 'A<T>' is not a pointer; did you mean to use '.'?}}
       c->x;
     }
 
     void instantiated(A a, A<T> b, T c) {
-      a->x; // expected-error {{member reference type 'A<int>' is not a pointer; did you mean to use '.'?}}
-            // expected-error@-1 {{no member named 'x' in 'N4::A<int>'}}
-      b->x; // expected-error {{member reference type 'A<int>' is not a pointer; did you mean to use '.'?}}
-            // expected-error@-1 {{no member named 'x' in 'N4::A<int>'}}
+      // FIXME: We should only emit a single diagnostic suggesting to use '.'!
+      a->x; // expected-error {{member reference type 'A<T>' is not a pointer; did you mean to use '.'?}}
+            // expected-error@-1 {{member reference type 'A<int>' is not a pointer; did you mean to use '.'?}}
+            // expected-error@-2 {{no member named 'x' in 'N4::A<int>'}}
+      b->x; // expected-error {{member reference type 'A<T>' is not a pointer; did you mean to use '.'?}}
+            // expected-error@-1 {{member reference type 'A<int>' is not a pointer; did you mean to use '.'?}}
+            // expected-error@-2 {{no member named 'x' in 'N4::A<int>'}}
       c->x; // expected-error {{member reference type 'int' is not a pointer}}
     }
   };
@@ -540,11 +543,10 @@ namespace N4 {
       a->T::f();
       a->T::g();
 
-      // FIXME: 'U' should be a dependent name, and its lookup context should be 'a.operator->()'!
-      a->U::x; // expected-error {{use of undeclared identifier 'U'}}
-      a->U::y; // expected-error {{use of undeclared identifier 'U'}}
-      a->U::f(); // expected-error {{use of undeclared identifier 'U'}}
-      a->U::g(); // expected-error {{use of undeclared identifier 'U'}}
+      a->U::x;
+      a->U::y;
+      a->U::f();
+      a->U::g();
     }
 
     void instantiated(D a) {

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need a release note? Also, can you point out the 'diff' from the previous commit?

I'm really not a fan of the 'out' parameter, I'd prefer perhaps returning a std::pair + structured binding. WDYT?

bool *NoArrowOperatorFound = nullptr);
ExprResult BuildOverloadedArrowExpr(Expr *Base, SourceLocation OpLoc,
bool *NoArrowOperatorFound,
bool &IsDependent);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooof, I'm almost always really against 'out' parameters in C++. I'd hope we could come up with a better way for this.

@@ -8002,15 +7991,26 @@ ExprResult Sema::ActOnStartCXXMemberReference(Scope *S, Expr *Base,
return ExprError();
}

bool IsDependent;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please initialize this to false anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// expected-error@-1 {{no member named 'x' in 'N4::A<int>'}}
b->x; // expected-error {{member reference type 'A<int>' is not a pointer; did you mean to use '.'?}}
// expected-error@-1 {{no member named 'x' in 'N4::A<int>'}}
// FIXME: We should only emit a single diagnostic suggesting to use '.'!
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unfortunate, it looks like we're having this problem diagnosing in both phases here, and can get worse with partial instantiations. Typically we'd want to solve this by replacing the expression with a RecoveryExpr or something to suppress it in the instantiated version, can you look into something like that?

@sdkrystian
Copy link
Member Author

Does this need a release note?

I don't think so, since this fixes a bug introduced in the same release.

Also, can you point out the 'diff' from the previous commit?

The condition of the assert in SemaExprCXX.cpp was changed from BaseType->isDependentType() to Base->isTypeDependent(), as was the condition on line 1318 in SemaExprMember.cpp.

I'm really not a fan of the 'out' parameter, I'd prefer perhaps returning a std::pair + structured binding. WDYT?

My initial solution was to return ExprEmpty(), but I changed to using an out parameter in response to review feedback (I don't like out parameters either).

@porglezomp
Copy link
Contributor

porglezomp commented Sep 23, 2024

I'm testing this and seeing some further behavior changes on dependent typenames with ->. I'm currently trying to reduce and determine if the behavior is correct or not.

Nevermind, it seems like this is an issue with our clang fork and doesn't impact this change.

@porglezomp
Copy link
Contributor

This appears to cause incorrect behavior in the following code:

struct Pointee {
    template<typename T>
    T *method() { return nullptr; }
};

template <typename T>
struct Pointer {
    T *pointer;
    T *operator->() { return pointer; }
};

struct Base {
    Pointer<Pointee> field;
};

template <typename T>
struct Example: public Base {
    bool example() {
        return field->method<T>() == nullptr;
    }
};

The field->method<T>() is diagnosed as being a dependent template name and it doesn't appear to be one.

@cor3ntin
Copy link
Contributor

@sdkrystian ping

@sdkrystian sdkrystian force-pushed the reapply-fix-overloaded-arrow branch from 0d4beec to 30297f7 Compare January 20, 2025 17:04
Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a release note?
There are still a few comments from @erichkeane that are not addressed.

Thanks!

@@ -8002,15 +7991,26 @@ ExprResult Sema::ActOnStartCXXMemberReference(Scope *S, Expr *Base,
return ExprError();
}

bool IsDependent;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sdkrystian
Copy link
Member Author

This appears to cause incorrect behavior in the following code:

struct Pointee {
    template<typename T>
    T *method() { return nullptr; }
};

template <typename T>
struct Pointer {
    T *pointer;
    T *operator->() { return pointer; }
};

struct Base {
    Pointer<Pointee> field;
};

template <typename T>
struct Example: public Base {
    bool example() {
        return field->method<T>() == nullptr;
    }
};

The field->method<T>() is diagnosed as being a dependent template name and it doesn't appear to be one.

@porglezomp I just pushed a commit that should fix it.

@sdkrystian
Copy link
Member Author

@erichkeane I pushed a commit (10ba129) which eliminates the out parameter in favor of building a CXXOperatorCallExpr with type ASTContext::DependentTy. WDYT?

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@sdkrystian sdkrystian merged commit 195a1fc into llvm:main Jan 22, 2025
8 checks passed
@sdkrystian
Copy link
Member Author

Reverting due to a failure building clang.

sdkrystian added a commit that referenced this pull request Jan 22, 2025
…ilding overloaded 'operator->' in the current instantiation (#104458)"" (#123982)

Reverts #109422
github-actions bot pushed a commit to arm/arm-toolchain that referenced this pull request Jan 22, 2025
…ext when building overloaded 'operator->' in the current instantiation (#104458)"" (#123982)

Reverts llvm/llvm-project#109422
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants